/**
 * Obtient le nom de la fonction
 * 
 * Il peut être "définit" ou redéfinit en créant l'attribut nameClass à la fonction / objet
 * 
 * @return {String} nom de la fonction ou "anonymous"
 */
Function.prototype.getName = function()
{
    var name = this.getInfo().name;
    if (typeof(this.nameClass) == "string")
	name = this.nameClass;
    else if (name == "")
	name = "anonymous";
    return name;
};

/**
 * Envoie un message d'erreur bloquant au navigateur (throw)
 * 
 * Le message s'affichera sous la forme:
 * nom de la classe: message
 * 
 * @param {String} message le message à afficher dans les erreurs
 */
Function.prototype.sendBlockingError = function(message)
{
    var name = this.getName();
    throw(getNewError("["+name+"] "+message));
};

/**
 * Obtient des informations sur la fonction
 * 
 * Un objet est renvoyé avec les attributs suivants:
 * - allfunc: la définition de la fonction entière (string)
 * - args: les arguments de la fonction (string)
 * - numArgs: le nombre d'arguments
 * - lengthBody: la longueur du body, sans espace au début et à la fin
 * - body: le corps de la fonction
 * - name: le nom de la fonction
 *
 * @return {Object} informations relative à la fonction 
 */
Function.prototype.getInfo = function()
{
    // Si cela a déjà été parsé, on les renvoie tel quel
    if (this._tgetInfo)
	return this._tgetInfo;
    
    var str = this.toString();
    
    // Regexp permettant d'obtenir: 1- le nom de la fonction, 2- les arguments, 3- le corps de la fonction
    // \s: un espace, \S: contraire d'un espace. Utilisé car l'option s des regexp n'existe pas en JS
    var reg = /^function([a-z0-9_\s]*)\(([^\)]*)\)[\s\S]*\{([\s\S]*)\}$/mi
    var matches = reg.exec(str);
    
    // On fait un trim pour avoir la longueur réelle du body sans les espaces aux extrémités
    var lengthBody = matches[3].trim().length
    
    // S'il y a des "," dans les arguments, il y en a au moins 1
    if (matches[2].indexOf(",") != -1)
      var numArgs = matches[2].split(",").length
    // Sinon, il n'y en a pas
    else
      var numArgs = 0
    
    // Objet renvoyé
    var obj = {
	allfunc: matches[0],
	args: matches[2],
	numArgs: numArgs,
	lengthBody: lengthBody,
	body: matches[3],
	name: matches[1].trim()
    };
    
    // Stockage des informations en cache, pour éviter de faire la regexp à chaque fois
    this._tgetInfo = obj;

    return obj;
};

/**
 * Permet de savoir si une fonction est "abstraite" par sa définition
 * 
 * Si la fonction possède un corps vide, alors elle l'est
 * 
 * @return {bool} true si elle est dite "abstraite", false sinon
 */
Function.prototype.isAbstract = function()
{
	if (typeof(this._abstract) == "boolean")
		return this._abstract;
    var info = this.getInfo();
    if (info.lengthBody == 0)
	return true;
    else
	return false;
};

/**
 * Implémente le "extends" des langages objets classiques, avec des classes abstraites
 * 
 * Pris en exemple de: http://phrogz.net/js/classes/OOPinJS2.html
 * 
 * @param {Class} parentClass le nom de la classe a hérité
 * @param {bool} isAbstract si la classe parente est une classe abstraite, mettre true, sinon rien mettre
 */
Function.prototype.$extends = function(parentClass){ 
	if (parentClass.constructor == Function) 
	{ 
		// Le nom de classes
		var nameSubclass = this.getName();
		var nameParent = parentClass.getName();
		var isAbstract = parentClass.isAbstract();
		// Les objets
		var thisobj = new this();
		var parentobj = new parentClass();
		
		var isGood = false;
		
		// Si on a une classe abstraite, on regarde les fonctions abstraites
		if (isAbstract)
		{
		  for (var i in parentobj)
		  {
		      var e = parentobj[i];
		      var te = thisobj[i];
		      if (typeof(e) == "function" && e.isAbstract())
		      {
			  // Si la fonction n'est pas défini
			  if (typeof(te) == "undefined")
			      this.sendBlockingError("Abstract function '"+i+"' of parent class "+nameParent+" must be defined in subclass "+nameSubclass+" !");
			  // Si le nom est déjà utilisé
			  else if (typeof(te) != "function")
			      this.sendBlockingError("Abstract function '"+i+"' of parent class "+nameParent+" must be defined in subclass "+nameSubclass+" and be type of 'function' ! (it is currently a '"+typeof(te)+"')");
			  // S'il y a pas assez d'arguments
			  else if (typeof(te) == "function" && te.getInfo().numArgs != e.getInfo().numArgs)
			      this.sendBlockingError("Abstract function '"+i+"' of parent class "+nameParent+" must be defined in subclass "+nameSubclass+" and must have "+e.getInfo().numArgs+" arguments ! (it has currently "+te.getInfo().numArgs+")");
			  else
			      isGood = true;
		      }
		  }
		}
		else
		    isGood = true;
		if (isGood)
		{
		  //console.log("[OK] All abstracts function are defined in "+this.getInfo().name);
		  
		  this.prototype = new parentClass;
		  this.prototype.constructor = this;
		  this.prototype.parent = parentClass.prototype;
		  // Le super, différente classe au cas où que prototype est utilisé autre part
		  this.prototype.super = new parentClass;
		  this.prototype.getClassName = function() { return this.getName(); }
		}
	}
	return this;
};

/**
 * Permet de définir la classe en tant qu'abstraite
 *
 * Les fonctions vides seront à définir
 */
Function.prototype.$abstract = function()
{
	this._abstract = true;
};

/**
 * Permet à une classe d'utiliser le système d'erreur
 */
Function.prototype.$useError = function()
{
	this.prototype.sendError = function(mes)
	{
		this.constructor.sendBlockingError(mes);
	};
};
